CSharp
Mono
-
Mono is a cross-platform implementation of the Common Language Runtime (CLR) and the .NET Framework.
-
% Makes the .NET Framework available in non-Windows environments.
-
Supports languages running on the CLR, such as C#, VB.NET, and F#.
-
Name:
-
The name was chosen by the original team as a pun since "mono" means "monkey" in Spanish, and there was a tradition of using animal-related names in the open-source community (like GNU and Python).
-
-
In Godot:
-
Godot Mono == Mono Framework support to run C# scripts.
-
Mono was the natural choice because:
-
It is open source and compatible with Godot's open distribution goals.
-
Offers .NET standard compatibility to run C#.
-
Works well on various operating systems, like Linux, macOS, and Windows.
-
-
It is a separate edition because adding Mono increases the Godot binary size, and not all users need it.
-
Concepts
Access Control
Default
-
Class members (properties, methods, etc.):
-
Default:
private -
If you do not specify an access modifier, the member is private and accessible only within the class itself.
-
-
Top-level classes and structs:
-
Default:
internal -
If you do not specify a modifier, the class or struct is accessible only within the same assembly (usually the same project in .NET).
-
Modifiers
abstract
-
Quick explanation of the 'abstract' keyword .
-
Good video.
-
static
-
"Static variables are used to pass information across different instances of the same class, not across different classes".
Classes
using System;
class Person
{
// Properties
public string Name { get; set; }
public int Age { get; set; }
// Constructor
public Person(string name, int age)
{
Name = name;
Age = age;
}
// Method to display information
public void DisplayInfo()
{
Console.WriteLine($"Name: {Name}, Age: {Age}");
}
}
class Program
{
static void Main(string[] args)
{
// Creating an instance of the Person class
Person person = new Person("John", 25);
person.DisplayInfo();
}
}
Set / Get
-
Tutorial and examples of simplified Set/Get usage: ChristianHur video
-
Without access control for Set and Get:
public String car;
-
Simplified:
public String car {get; private set;}-
Using
{ set; get; }creates an automatic property . This means the compiler automatically generates a private field to store the value, without needing to explicitly declare it in the class. -
Equivalent to doing:
private String _car; // Manually generated private field public String car { get { return _car; } set { _car = value; } }
-
-
Normal:
public String car { get { return car; } private set { car = value; } }
Delegates (Callables in GDScript)
// Stores the function, making the variable a delegate.
variable = function;
// Stores the function's return value.
variable = function();
Generics
-
"Work with multiple Types".
void function<T>(T a, T b) {
}
function<float>(a, b);
Import and Export
Godot
-
.
Unity
-
[SerializeField]-
In Unity, exposes the property in the Inspector.
-
Events
Publisher
-
Uses Invoke.
Subscriber
-
'subscribe' is done via 'myEvent += created_function'.
-
'unsubscribe' is done via 'myEvent -= created_function'.
Creating events
-
Publisher:
using System;
public class MyScript_A : MonoBehaviour {
// Creates the event, using 'arguments' like the event's 'EventArgs'.
public event EventHandler<Arguments> OnPressSpace;
// Function used as the 'Generic Parameter' for 'OnPressSpace'.
public class Arguments : EventArgs {
public int value;
}
private void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
// Fires the event with its argument.
OnPressSpace?.Invoke(this, new Arguments {value = 2});
}
}
}
-
Subscriber:
using System;
public class MyScript_B : MonoBehaviour {
private Start() {
// Sets the variable 'myScript_A' to type 'MyScript_A' and stores the 'MyScript_A' component.
MyScript_A myScript_A = GetComponent<MyScript_A>();
// Subscribes: Accesses 'OnPressSpace' event from 'myScript_A' and "adds" the created function 'Event_OnPressSpace'.
myScript_A.OnPressSpace += Event_OnPressSpace;
}
// Uses the created function 'Event_OnPressSpace' to receive the 'object' and 'arguments' passed during event firing.
private void Event_OnPressSpace(object sender, myScript_A.Arguments e) {
Debug.log("Value: " + e.value);
}
}
Godot
Impressions
-
I MUCH prefer using C++ over C# for Godot.
-
I don't like the strange performance, PascalCase and camelCase, and various quirks of the language.
-
C++ gives me more freedom inside the engine, with the possibility to contribute and customize Godot, and it is a more interesting challenge.
-
I didn't like the support for Godot at all, with many limitations and annoying issues.
-
I think the syntax is ugly and somewhat outdated.
About
-
Default: Private / Internal.
-
"A lot of features you see now in C# were borrowed from Swift" - Person who made Mono.
-
There has been C# support since Godot 3.0.
Status
-
(2024-11-21) "There's work right now to move CSharp to GDExtension".
-
Problems :
-
Problems .
-
Writing editor plugins is possible, but it is currently quite convoluted.
-
State is currently not saved and restored when hot-reloading, except for exported variables.
-
Attached C# scripts should refer to a class whose class name matches the file name.
-
There are some methods such as
Get()/Set(),Call()/CallDeferred()and the signal connection methodConnect()that rely on Godot'ssnake_caseAPI naming conventions. So when using e.g.CallDeferred("AddChild"),AddChildwill not work because the API expects the originalsnake_caseversionadd_child.
-
-
Modifying values:
-
You might encounter the following error when trying to modify some values in Godot objects, e.g., when trying to change the X coordinate of a
Node2D:
public partial class MyNode2D : Node2D { public override void _Ready() { Position.X = 100.0f; // CS1612: Cannot modify the return value of 'Node2D.Position' because // it is not a variable. } }-
Structs (in this example, a
Vector2) in C# are copied on assignment, meaning that when you retrieve such an object from a property or an indexer, you get a copy of it, not the object itself. Modifying said copy without reassigning it afterwards won't achieve anything. -
The workaround is simple: retrieve the entire struct, modify the value you want to change, and reassign the property.
var newPosition = Position; newPosition.X = 100.0f; Position = newPosition;-
Since C# 10, it is also possible to use with expressions on structs, allowing you to do the same thing in a single line.
Position = Position with { X = 100.0f };-
Sometimes this boilerplate must be done to improve C# performance compared to GDScript, as explained .
-
-
C# GC (Garbage Collector) issues .
-
When things accumulate, a Stop The World occurs to clean everything.
-
No GC language can fully solve this problem.
-
-
Band-aids to minimize garbage accumulation to avoid GC issues:
-
Do not allocate objects during gameplay.
-
Doesn't always solve it.
-
-
"Just pool objects".
-
"Collect on every scene load".
-
"Clears every scene change, it's fine".
-
-
-
-
Building
-
You need to (re)build the project assemblies whenever you want to see new exported variables or signals in the editor. This build can be manually triggered by clicking the Build button in the top right corner of the editor.
-
You will also need to rebuild the project assemblies to apply changes in "tool" scripts.
API Differences
-
-
wow.... so much bad stuff.
-
-
@GlobalScope
-
Functions normally in global scope in GDScript, like Godot's
printfunction, are available in theGDstatic class which is part of theGodotnamespace.-
GD.Print("Hello from C# to Godot");
-
-
.